#pragma once

#include <iostream>
#include <unordered_map>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include "Log.hpp"
#include "Util.hpp"
#include "Buffer.hpp"

using namespace ns_log;
using namespace ns_util;

#define LINE_END "\r\n"

const int RECV_BUF_SIZE = 4096;
const int METHOD_SIZE = 4;
const int PATH_SIZE = 128;
const int VERSION_SIZE = 10;
const int KEY_SIZE = 20;
const int VALUE_SIZE = 256;
const int SEND_SIZE = 1024; // 一次发送大小

#define OK 200
#define MOVED_PERMANENTLY 301
#define TEMPORARY_REDIRECT 307
#define BAD_REQUEST 400
#define FORBIDDEN 403
#define NOT_FOUND 404
#define SERVER_ERROR 500
#define BAD_GATEWAY 504

// #define MSG_SEND_AUTO

enum FileType
{
    DIR_FILE,
    NORMAL_FILE
};

struct HttpResponse;
typedef void (*ResponseBody)(const HttpResponse &httpResponse, Buffer *sendbuf, int socket);

struct HttpResponse
{
    // 响应行
    std::string desc;
    int status_code;
    std::string version;
    // 响应头
    std::unordered_map<std::string, std::string> headers;
    int contentLength = -1;
    // 报文
    std::string bodys;
    std::string filename;
    FileType fileType;
    ResponseBody sendResponseBodyFunc;
};

struct HttpRequest
{
    // 请求行
    std::string method;
    std::string url;
    std::string version;
    // 请求头
    std::unordered_map<std::string, std::string> headers;
    // 请求报文
    std::string bodys;
};

class Http
{
public:
    void ParseHttpRequest(Buffer *readBuf)
    {
        ParseHttpRequestLine(readBuf);
        ParseHttpRequestHeaders(readBuf);
    }
    void ProcessHttpRequest()
    {
        //  判断是否是get请求方法
        if (strcasecmp(_httpRequest.method.c_str(), "get") != 0)
        {
            return;
        }

        std::string file;
        // 解码
        _httpRequest.url = EnDecodeUtil::Decode(_httpRequest.url);

        // 处理客户端请求资源
        if (_httpRequest.url == "/")
        {
            file = "./";
        }
        else
        {
            file = _httpRequest.url.substr(1);
        }
        // LOG(DEBUG) << file << std::endl;
        // file = EnDecodeUtil::Decode(file);

        // 判断文件类型
        struct stat st;
        if (stat(file.c_str(), &st) < 0)
        {
            // 文件不存在
            _httpResponse.filename = "404.html";
            _httpResponse.fileType = NORMAL_FILE;
            _httpResponse.status_code = NOT_FOUND;
            _httpResponse.headers.insert({"Content-Type", "text/html"});
            _httpResponse.sendResponseBodyFunc = SendFile;
            std::cout << file << std::endl;
            return;
        }

        if (S_ISDIR(st.st_mode))
        {
            // 目录
            _httpResponse.fileType = DIR_FILE;
            _httpResponse.headers.insert({"Content-Type", "text/html"});
            _httpResponse.sendResponseBodyFunc = SendDir;
        }
        else
        {
            // 文件
            _httpResponse.fileType = NORMAL_FILE;
            _httpResponse.headers.insert(make_pair("Content-Type", GetContentType(file)));
            _httpResponse.contentLength = st.st_size;
            _httpResponse.sendResponseBodyFunc = SendFile;
        }
        _httpResponse.status_code = OK;
        _httpResponse.filename = file;
    }
    void BuildHttpResponse(Buffer *sendbuf, int socket)
    {
        if (_httpResponse.status_code > 500)
        {
            LOG(INFO) << "\n\nstatus code: " << _httpResponse.status_code << "\n\n";
        }
        // 状态行
        std::string line = "HTTP/1.1 " + std::to_string(_httpResponse.status_code) + " " + Code2Desc(_httpResponse.status_code) + LINE_END;
        sendbuf->AppendData(line);
        std::cout << line;
        //  响应报头
        FillContentLength();
        for (auto &iter : _httpResponse.headers)
        {
            sendbuf->AppendData(iter.first);
            sendbuf->AppendData(": ");
            sendbuf->AppendData(iter.second + LINE_END);
            std::cout << iter.first << ": " << iter.second << LINE_END;
        }
        // 响应空行
        sendbuf->AppendData(LINE_END);
#ifndef MSG_SEND_AUTO
        if (sendbuf->SendData(socket) == -1)
            return;
#endif
        // 响应报文
        _httpResponse.sendResponseBodyFunc(_httpResponse, sendbuf, socket);
    }

private:
    void ParseHttpRequestLine(Buffer *readBuf)
    {
        std::string line = readBuf->ReadLine();
        std::cout << line << std::endl;
        char method[METHOD_SIZE];
        char path[PATH_SIZE];
        char version[VERSION_SIZE];

        sscanf(line.c_str(), "%[^ ] %[^ ]%s", method, path, version);
        _httpRequest.method = method;
        _httpRequest.url = path;
        _httpRequest.version = version;
    }
    void ParseHttpRequestHeaders(Buffer *readBuf)
    {
        while (1)
        {
            std::string line = readBuf->ReadLine();
            if (line.size() == 0)
                break;
            std::string key;
            std::string value;
            StringUtil::CutString(line, key, value, ": ");
            if (key == "User-Agent" || key == "Range")
                std::cout << line << std::endl;
            _httpRequest.headers.insert({move(key), move(value)});
        }
    }
    void ParseHttpRequesBodys(Buffer *readBuf)
    {
        auto iter = _httpRequest.headers.find("Content-Length");
        if (iter != _httpRequest.headers.end())
        {
            // 读取报文
        }
    }
    void FillContentLength()
    {
        if (_httpResponse.contentLength == -1)
        {
            if (_httpResponse.fileType == DIR_FILE)
                FillDirContentLength(_httpResponse.filename);
            else
                FillFileContentLength(_httpResponse.filename);
        }
        _httpResponse.headers.insert(make_pair("Content-Length", std::to_string(_httpResponse.contentLength)));
    }
    static void SendFile(const HttpResponse &httpResponse, Buffer *sendbuf, int socket)
    {
        std::string filename = httpResponse.filename;
        int fd = open(filename.c_str(), O_RDONLY);
        if (fd < 0)
        {
            LOG(ERROR) << "open " << filename << " error\n";
            return;
        }
        bool flag = false;
        // if (httpResponse.contentLength == 2460889 || httpResponse.contentLength == 2054941)
        //    flag = true;
        int total = 0;
        while (true)
        {
            char buf[1024] = {0};
            ssize_t sz = read(fd, buf, sizeof(buf));
            if (sz > 0)
            {
                total += sz;
                sendbuf->AppendData(buf, sz);
                // LOG(INFO) << sz << "\n";
#ifndef MSG_SEND_AUTO
                if (sendbuf->SendData(socket) == -1)
                {
                    LOG(INFO) << "write close\n";
                    return;
                }
#endif
            }
            else if (sz == 0)
            {
                // 读完了
                break;
            }
            else
            {
                LOG(ERROR) << "read error\n";
                close(fd);
                return;
            }
            if (flag && total % (1024 * 100) == 0)
            {
                std::cout << "total: " << total << std::endl;
            }
        }
        std::cout << "total: " << total << std::endl;
        // off_t offset = 0;
        // int size = httpResponse.contentLength;
        // // LOG(DEBUG) << size << "\n";
        // while (offset < size)
        // {
        //     int ret = sendfile(_sock, fd, &offset, SEND_SIZE);
        //     if (ret < 0 && errno != EAGAIN)
        //     {
        //         LOG(ERROR) << ret << " sendfile error: " << strerror(errno) << "\n";
        //         break;
        //     }
        //     // LOG(DEBUG) << ret << " " << offset << "\n";
        //     // usleep(20);
        // }
        // LOG(DEBUG) << offset << "\n";
        close(fd);
    }
    static void SendDir(const HttpResponse &httpResponse, Buffer *sendbuf, int socket)
    {
        sendbuf->AppendData(httpResponse.bodys.c_str());
#ifndef MSG_SEND_AUTO
        if (sendbuf->SendData(socket) == -1)
        {
            LOG(INFO) << "write close\n";
            return;
        }
#endif
    }
    // 用来填充content-length
    void FillDirContentLength(const std::string &dirname)
    {
        // 函数内进行空间申请，需要手动释放
        struct dirent **namelist;
        int num = scandir(dirname.c_str(), &namelist, nullptr, alphasort);
        if (namelist == nullptr)
        {
            LOG(INFO) << "namelist nullptr\n";
            exit(-1);
        }
        std::string buf = "<html><head><meta charset=\"UTF-8\"><title>" + dirname + "</title></head><body><table>";

        for (int i = 0; i < num; ++i)
        {
            std::string name = namelist[i]->d_name;
            std::string filename = dirname + name;

            struct stat st;
            stat(filename.c_str(), &st);

            if (S_ISDIR(st.st_mode))
            {
                buf += "<tr><td><a href=\"" + name + "/\">" + name + "</td>";
                buf += "<td>" + std::to_string(st.st_size) + "</td></tr>";
            }
            else
            {
                buf += "<tr><td><a href=\"" + name + "\">" + name + "</td>";
                buf += "<td>" + std::to_string(st.st_size) + "</td></tr>";
            }
            free(namelist[i]);
        }

        buf += "</table></body></html>";
        _httpResponse.contentLength = buf.size();
        _httpResponse.bodys = move(buf);

        free(namelist);
    }
    void FillFileContentLength(const std::string &filename)
    {
        struct stat st;
        stat(filename.c_str(), &st);
        _httpResponse.contentLength = st.st_size;
    }
    static std::string Code2Desc(int code)
    {
        std::string desc;
        switch (code)
        {
        case OK:
            desc = "OK";
            break;
        case NOT_FOUND:
            desc = "NOT FOUND";
            break;
        default:
            LOG(INFO) << code << "\n";
            desc = "UNKNOW";
            break;
        }

        return desc;
    }
    std::string GetContentType(const std::string &filename)
    {
        // std::cout << __LINE__ << ": " << filename << std::endl;
        size_t pos = filename.find_last_of(".");
        // std::cout << __LINE__ << ": " << pos << std::endl;
        std::string suffix;
        if (pos == std::string::npos)
        {
            goto END;
        }
        suffix = filename.substr(pos);
        if (suffix == ".html")
            return "text/html";
        else if (suffix == ".dwg")
            return "application/x-dwg";
        else if (suffix == ".gif")
            return "image/gif";
        else if (suffix == ".gp4")
            return "application/x-gp4";
        else if (suffix == ".ico")
            return "image/x-icon";
        else if (suffix == ".img")
            return "application/x-img";
        else if (suffix == ".jpg")
            return "image/jpeg";
        else if (suffix == ".mp4")
            return "video/mpeg4";
        else if (suffix == ".png")
            return "application/x-png";
        else if (suffix == ".ogg")
            return "application/ogg";
        else if (suffix == ".wav")
            return "audio/wav";
    END:
        return "text/plain; charset=utf-8";
    }

private:
    // int _sock = -1;
    HttpRequest _httpRequest;
    HttpResponse _httpResponse;
};